﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Noris.Tools.SystemResourceMonitor
{
    /// <summary>
    /// Monitor, vlastní výkonná třída
    /// </summary>
    public class ResMonitor
    {
        #region Konstrukce, proměnné, thread
        /// <summary>
        /// Konstruktor, zajistí načtení konfigurace
        /// </summary>
        public ResMonitor()
        {
            this._PerfInit();
            this.ConfigLoad();
            this.Interval = 1000;
            this._TraceDirectoryValid = null;
            this._StartThread();
        }
        private void _StartThread()
        {
            this.AutoReset = new System.Threading.AutoResetEvent(false);

            this.WorkThread = new System.Threading.Thread(this._ThreadLoop);
            this.WorkThread.IsBackground = true;
            this.WorkThread.Name = "monitor";
            this.WorkThread.Priority = System.Threading.ThreadPriority.BelowNormal;
            this.WorkThread.TrySetApartmentState(System.Threading.ApartmentState.MTA);
            this.WorkThread.Start();
        }
        private void _ThreadLoop()
        {
            this.ScanInit();
            this.ScanLoop();
            this.WorkThread = null;
        }
        private System.Threading.Thread WorkThread;
        private System.Threading.AutoResetEvent AutoReset;
        private bool _AbortRequest;
        private int _ScanCount;
        private int _CurrentProcessId;
        #endregion
        #region Config
        private void ConfigLoad()
        {
            this._MonitorConfig = new MonitorConfig();
        }
        private void ConfigSave()
        {
            this._MonitorConfig.Save();
        }
        private MonitorConfig _MonitorConfig;
        #endregion
        #region PerformanceCounters
        private void _PerfInit()
        {
            //try
            //{
            //    this._PerfCpu = new System.Diagnostics.PerformanceCounter
            //        ("Processor", "% Processor Time", "_Total", true);
            //    this._PerfRam = new System.Diagnostics.PerformanceCounter
            //        ("Memory", "Available MBytes", true);
            //}
            //catch(Exception)
            //{}
        }
        private string _PerfTextCpu
        {
            get
            {
                if (this._PerfCpu != null)
                    return this._PerfCpu.NextValue().ToString();
                return "";
            }
        }
        private string _PerfTextRam
        {
            get
            {
                if (this._PerfRam != null)
                    return this._PerfRam.NextValue().ToString();
                return "";
            }
        }
        private System.Diagnostics.PerformanceCounter _PerfCpu;
        private System.Diagnostics.PerformanceCounter _PerfRam;
        #endregion
        #region Public property, eventy a metody
        #region Running a spol
        /// <summary>
        /// Klíčová property: říká, zda monitorování běží nebo ne. 
        /// Lze setovat. Na hodnotu false kdykoliv. Setování na true nemusí být úspěšné (zůstane Running = false), pak jsou informace o problémech v property RunningInfo.
        /// </summary>
        public bool Running
        {
            get { return this._Running; }
            set
            {
                this.RunningInfo = "";
                if (!value)
                {
                    this._Running = false;
                    return;
                }
                bool intervalValid = this._IntervalIsValid();
                bool directoryValid = this._TraceDirectoryIsValid();
                if (intervalValid && directoryValid)
                {
                    if (!this._Running)
                    {
                        this._Running = true;
                        this.AutoReset.Set();     // Probudíme spící thread!
                    }
                    this.RunningInfo = "Monitor je aktivní.";
                }
                else
                {
                    this.RunningInfo = (intervalValid ? "" : this._IntervalInfo) + 
                                       (directoryValid ? "" : "Adresář není přípustný.");
                    this._Running = false;
                }
            }
        }
        /// <summary>
        /// Textová informace o výsledku nastavení this.Running (může obsahovat text s chybou, pro nešlo nastavit stav true).
        /// </summary>
        public string RunningInfo { get; private set; }
        /// <summary>
        /// Hodnota Running, její setování neřeší logiku monitoru, ale vyvolává event RunningChanged.
        /// </summary>
        private bool _Running
        {
            get { return this.__Running; }
            set
            {
                if (value != this.__Running)
                {
                    this.__Running = value;
                    OnRunningChanged();
                }
            }
        }
        private bool __Running;
        /// <summary>
        /// Událost po skutečné změně hodnoty Running.
        /// Pozor, k události dojde jak po cílené změně Running, tak po závažné chybě v procesu kdy se Running shodí na false.
        /// </summary>
        public event EventHandler RunningChanged;
        protected virtual void OnRunningChanged()
        {
            if (this.RunningChanged != null)
                this.RunningChanged(this, EventArgs.Empty);
        }
        #endregion
        #region Interval a spol
        /// <summary>
        /// Interval měření (v milisekundách), v rozsahu 100 ÷ 900000 (=0,1 sec ÷ 15 minut)
        /// </summary>
        public int Interval
        {
            get { return this._Interval; }
            set
            {
                int interval = (value < 100 ? 100 : (value > 900000 ? 900000 : value));
                if (interval > this._Interval)
                {   // Prodlužujeme interval => nebudeme spáče budit (aktuální interval necháme ten menší, až příští bude delší):
                    this._Interval = interval;
                }
                else if (interval < this._Interval)
                {
                    this._Interval = interval;
                    // AutoReset.Set() => Vzbudíme spáče (protože interval mohl být i 15 minut, a změna například na 1 sekundu by se realizovala až po původních 15 minutách):
                    this.AutoReset.Set();
                }
            }
        }
        private bool _IntervalIsValid()
        {
            bool isValid = (this._Interval >= 100 && this._Interval <= 900000);
            this._IntervalInfo = (isValid ? "" : "Zadaný interval není platný.");
            return isValid;
        }
        /// <summary>
        /// Interval měření (v milisekundách), fyzicky je uložený v Configu
        /// </summary>
        private int _Interval { get { return this._MonitorConfig.Interval; } set { this._MonitorConfig.Interval = value; } }
        private string _IntervalInfo;
        #endregion
        #region TraceDirectory, TraceFile a spol
        /// <summary>
        /// Výstupní adresář. Změna se promítná ihned = pro příští scan, pokud je stav Running.
        /// </summary>
        public string TraceDirectory
        {
            get { return this._TraceDirectory; }
            set { this._TraceDirectory = value; this._TraceDirectoryValid = null; }
        }
        /// <summary>
        /// Vrátí plný název trace souboru. Pokud je nový, pak už obsahuje záhlaví. 
        /// Řeší: adresář, jeho platnost, jeho vytvoření. Název souboru (datum, pořadové číslo), jeho velikost, jeho vytvoření.
        /// </summary>
        /// <returns></returns>
        private string _GetTraceFile()
        {
            string path = this._TraceDirectory;
            if (!this._TraceDirectoryValid.HasValue)
                this._TraceDirectoryValid = _TraceDirectoryIsValid();

            if (!this._TraceDirectoryValid.HasValue || !this._TraceDirectoryValid.Value)
                return null;

            string file = System.IO.Path.Combine(path, "SysMonitor_" + DateTime.Now.ToString("yyMMdd") + "_001.csv");
            if (!System.IO.File.Exists(file))
                this._WriteHeaderToTrace(file);

            return file;
        }
        /// <summary>
        /// Adresář, fyzicky je uložený v Configu
        /// </summary>
        private string _TraceDirectory { get { return this._MonitorConfig.TraceDirectory; } set { this._MonitorConfig.TraceDirectory = value; } }
        /// <summary>
        /// Vrací informaci (true / false) o tom, zda adresář this._TraceDirectory je korektní.
        /// Pokud není zadán, vrací false.
        /// Pokud neexistuje, zkusí jej vytvořit.
        /// Pokud ve výsledku existuje, vrací true.
        /// </summary>
        /// <returns></returns>
        private bool _TraceDirectoryIsValid()
        {
            string path = this._TraceDirectory;
            bool isValid = false;
            if (!String.IsNullOrEmpty(path))
            {
                try
                {
                    System.IO.DirectoryInfo dirInfo = new System.IO.DirectoryInfo(path);
                    if (!dirInfo.Exists)
                    {
                        dirInfo.Create();
                        dirInfo.Refresh();
                    }
                    isValid = dirInfo.Exists;
                }
                catch (Exception exc)
                {
                    isValid = false;
                }
            }
            return isValid;
        }
        private bool? _TraceDirectoryValid;
        #endregion
        public bool MonitorSystemProcesses { get; set; }
        /// <summary>
        /// Počet provedených scanů od spuštění. V době pozastavení (příznak Running == false) se nenuluje.
        /// </summary>
        public int ScanCount { get { return this._ScanCount; } }
        public void Abort()
        {
            this._AbortRequest = true;
            this.AutoReset.Set();             // Zazvoníme do výkonného vlákna.
        }
        public event EventHandler ScanProcessAfter;
        /// <summary>
        /// Vyvolá event ScanProcessAfter
        /// </summary>
        protected virtual void OnScanProcessAfter()
        {
            if (this.ScanProcessAfter != null)
                this.ScanProcessAfter(this, EventArgs.Empty);
        }
        #endregion
        #region Scan procesů
        private void ScanInit()
        {
            this._AbortRequest = false;
            this._ScanCount = 0;
            this._ProcessDict = null;
            this._CurrentProcessId = System.Diagnostics.Process.GetCurrentProcess().Id;
        }
        private void ScanLoop()
        {
            this._PreviousScan = null;
            while (!this._AbortRequest)
            {
                this.AutoReset.Reset();

                if (!this._Running)
                {   // Run je zastaven: zapomenu čas posledního běhu, vypnu paměť a jdu spát...
                    this._PreviousScan = null;
                    this._ProcessDict = null;
                    this.AutoReset.WaitOne();                  // Nemáme běžet: počkáme si na odblokování.
                }

                if (this._AbortRequest)
                    break;

                if (this._Running)
                {
                    DateTime start = DateTime.Now;             // Zajistí přesnou délku jednoho každého cyklu...
                    this.ScanProcess();
                    this.OnScanProcessAfter();
                    DateTime next = start.AddMilliseconds(this._Interval);
                    int miliseconds = (int)((next - DateTime.Now).TotalMilliseconds) + 1;
                    if (miliseconds > 0)
                        this.AutoReset.WaitOne(miliseconds);   // Počkáme na doběhnutí času, nebo na probuzení odjinud
                }
            }
        }
        private void ScanProcess()
        {
            string traceFile = this._GetTraceFile();
            if (traceFile == null) return;

            try
            {
                List<ProcessPairInfo> processPairs = this.GetCurrentProcesses();
                using (System.IO.StreamWriter writter = new System.IO.StreamWriter(traceFile, true, Encoding.UTF8))
                {
                    foreach (ProcessPairInfo processPair in processPairs)
                        this.ScanProcessOne(processPair, writter);
                }
                this._ScanCount++;
            }
            catch (Exception exc)
            { }
        }
        /// <summary>
        /// Načte aktuální procesy, porovná je s předešlým stavem (určí tak rozdíly a využití procesoru), 
        /// aktuální stav zaznamená do paměti.
        /// Řeší výpis systémových procesů (detailně / sumárně), vrátí soupis všech procesů vhodných k výpisu.
        /// </summary>
        /// <returns></returns>
        private List<ProcessPairInfo> GetCurrentProcesses()
        {
            bool isFirstScan = (this._ProcessDict == null);
            if (isFirstScan)
                this._ProcessDict = new Dictionary<int, ProcessInfo>();
            else
            {
                foreach (ProcessInfo processInfo in this._ProcessDict.Values)
                    processInfo.Exists = false;
            }

            List<ProcessPairInfo> result = new List<ProcessPairInfo>();

            DateTime now = DateTime.Now;
            System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();
            foreach (System.Diagnostics.Process process in processes)
            {
                int processId = process.Id;
                ProcessInfo previous = null;
                bool hasPrevious = this._ProcessDict.TryGetValue(processId, out previous);

                try
                {
                    ProcessInfo current = new ProcessInfo(process, now, this._CurrentProcessId, isFirstScan, hasPrevious);
                    ProcessPairInfo pair = new ProcessPairInfo(this._PreviousScan, previous, current);
                    result.Add(pair);

                    if (hasPrevious)
                        this._ProcessDict[processId] = current;
                    else
                        this._ProcessDict.Add(processId, current);
                }
                catch (Exception exc)
                { }
            }

            // Z dictionary odeberu ty procesy, které v aktuálním seznamu nejsou (=zmizely):
            List<ProcessInfo> removedProcesses = this._ProcessDict.Values.Where(pi => !pi.Exists).ToList();
            foreach (ProcessInfo removedProcess in removedProcesses)
                this._ProcessDict.Remove(removedProcess.Id);

            this._PreviousScan = now;

            return result;
        }
        private void ScanProcessOne(ProcessPairInfo processPair, System.IO.StreamWriter writter)
        {
            bool isEnabled = this.IsScanOfProcessEnabled(processPair.CurrentInfo);
            if (isEnabled)
                writter.WriteLine(processPair.TraceLine);
        }
        private DateTime? _PreviousScan = null;
        private Dictionary<int, ProcessInfo> _ProcessDict;
        private bool IsScanOfProcessEnabled(ProcessInfo process)
        {
            if (process.IsCurrentProcess) return false;
            if (!this.MonitorSystemProcesses && process.IsSystem) return false;
            return true;
        }
        #endregion
        #region Scan computeru do titulkového řádku
        /// <summary>
        /// Do daného trace souboru zapíše záhlaví
        /// </summary>
        /// <param name="file"></param>
        private void _WriteHeaderToTrace(string file)
        {
            
            //System.Environment.Is64BitOperatingSystem;
            //System.Environment.MachineName;
            //System.Environment.OSVersion.Platform.ToString();
            //System.Environment.OSVersion.ServicePack;
            //System.Environment.OSVersion.VersionString;
            //System.Environment.ProcessorCount;
            //System.Environment.SystemDirectory;
            //System.Environment.UserDomainName;
            //System.Environment.UserName;
            //System.Environment.Version;

            //System.Windows.Forms.SystemInformation.ComputerName;
            //System.Windows.Forms.SystemInformation.Network;
            //System.Windows.Forms.SystemInformation.UserDomainName;
            //System.Windows.Forms.SystemInformation.UserName;


            string title = ProcessPairInfo.TraceTitle + Environment.NewLine;
            System.IO.File.WriteAllText(file, title, Encoding.UTF8);
        }
        #endregion
    }
    #region class MonitorConfig : konfigurace monitoringu
    /// <summary>
    /// Data obsahující konfiguraci
    /// </summary>
    public class MonitorConfig
    {
        #region Konstrukce, Load, Save
        public MonitorConfig()
        {
            this.Load();
        }
        internal void Load()
        {
            string configFile = this.ConfigFile;
            try
            {
                if (System.IO.File.Exists(configFile))
                    this._LoadFrom(configFile);
            }
            catch (Exception)
            { }
            this._CheckDefaultValues();
        }
        private void _LoadFrom(string configFile)
        {
            XDocument xDoc = XDocument.Load(configFile);
            XElement xRoot = xDoc.Root;
            if (xRoot.Name.LocalName != "monitor") return;
            string version = xRoot.GetAttributeString("Version");

            XElement xConfig = xRoot.Element("configuration");
            this.RunOnStart = xConfig.GetAttributeBoolean("RunOnStart");
            this.Interval = xConfig.GetAttributeInt32("Interval");
            this.TraceDirectory = xConfig.GetAttributeString("TraceDirectory");
            this.SystemProcessDetail = xConfig.GetAttributeBoolean("SystemProcessDetail");
            this.MonitorThisProcess = xConfig.GetAttributeBoolean("MonitorThisProcess");
            this.SkipMonitorSameText = xConfig.GetAttributeBoolean("SkipMonitorSameText");
            this.SkipMonitorSameLine = xConfig.GetAttributeBoolean("SkipMonitorSameLine");

        }
        private void _CheckDefaultValues()
        {
            this.Interval = (this.Interval < INTERVAL_MIN ? INTERVAL_MIN : (this.Interval > INTERVAL_MAX ? INTERVAL_MAX : this.Interval));
            if (String.IsNullOrEmpty(this.TraceDirectory))
                this.TraceDirectory = System.IO.Path.Combine(this._ExecutablePath, "MonitorData");
        }
        internal void Save()
        {
            string configFile = this.ConfigFile;

            XDocument xDoc = new XDocument();
            XElement xMonitor = new XElement("monitor");
            xMonitor.AddAttribute("Version", "1.0");
            xDoc.Root.Add(xMonitor);

            XElement xConfig = new XElement("configuration");
            xConfig.AddAttribute("RunOnStart", this.RunOnStart);
            xConfig.AddAttribute("Interval", this.Interval);
            xConfig.AddAttribute("TraceDirectory", this.TraceDirectory);
            xConfig.AddAttribute("SystemProcessDetail", this.SystemProcessDetail);
            xConfig.AddAttribute("MonitorThisProcess", this.MonitorThisProcess);
            xConfig.AddAttribute("SkipMonitorSameText", this.SkipMonitorSameText);
            xConfig.AddAttribute("SkipMonitorSameLine", this.SkipMonitorSameLine);
            xMonitor.Add(xConfig);

            xDoc.Save(configFile, true);
        }
        private string ConfigFile
        {
            get
            {
                if (this._ConfigFile == null)
                {
                    this._ExecutableFile = System.Windows.Forms.Application.ExecutablePath;
                    this._ExecutablePath = System.IO.Path.GetDirectoryName(this._ExecutableFile);
                    this._ExecutableName = System.IO.Path.GetFileNameWithoutExtension(this._ExecutableFile);
                    this._ConfigFile = System.IO.Path.Combine(this._ExecutablePath, this._ExecutableName) + ".config.xml";
                }
                return this._ConfigFile;
            }
        }
        private string _ExecutableFile;
        private string _ExecutablePath;
        private string _ExecutableName;
        private string _ConfigFile;
        #endregion
        #region Public property a konstanty
        public bool RunOnStart { get; set; }
        public int Interval { get; set; }
        public string TraceDirectory { get; set; }
        public bool SystemProcessDetail { get; set; }
        public bool MonitorThisProcess { get; set; }
        public bool SkipMonitorSameText { get; set; }
        public bool SkipMonitorSameLine { get; set; }
        public const int INTERVAL_MIN = 100;
        public const int INTERVAL_MAX = 900000;
        #endregion
    }
    #endregion
    #region class ProcessInfo : data o jednom procesu
    /// <summary>
    /// ProcessInfo : data o jednom procesu
    /// </summary>
    public class ProcessInfo
    {
        #region Konstrukce a property
        public ProcessInfo(System.Diagnostics.Process process, DateTime now, int currentProcessId, bool isFirstScan, bool hasPrevious)
        {
            this.Now = now;
            this.Id = process.Id;
            this.IsCurrentProcess = (this.Id == currentProcessId);
            this.ProcessName = process.ProcessName;
            this.ThreadsCount = process.Threads.Count;
            this.MachineName = process.MachineName;
            Try(delegate { this.ProcessorAffinity = process.ProcessorAffinity.ToInt32(); });
            if (this.Id != 0)
            {   // ID == 0 je proces Iddle a jiné void procesy.
                Try(delegate { this.ModulesCount = process.Modules.Count; });

                this.WorkingSetKb64 = process.WorkingSet64 / 1024L;
                this.PrivateMemorySizeKb64 = process.PrivateMemorySize64 / 1024L;
                Try(delegate { this.MainWindowTitle = process.MainWindowTitle; });
                Try(delegate { this.TotalProcessorTime = process.TotalProcessorTime; });
                Try(delegate { this.UserProcessorTime = process.UserProcessorTime; });
                
                if (!hasPrevious)
                {
                    Try(delegate { this.StartTime = process.StartTime; });
                    if (this.ModulesCount.HasValue)
                    {
                        Try(delegate { this.FileName = process.MainModule.FileName; });
                        Try(delegate { this.MainModuleName = process.MainModule.ModuleName; });
                    }
                    if (process.StartInfo != null)
                    {
                        Try(delegate { this.Arguments = process.StartInfo.Arguments; });
                        Try(delegate { this.Domain = process.StartInfo.Domain; });
                        Try(delegate { this.UserName = process.StartInfo.UserName; });
                    }
                    Try(delegate { this.ParentId = ParentProcessUtilities.GetParentProcessId(process.Handle); });
                }
            }

            this.ProcessPresence = (isFirstScan ? ProcessPresenceType.FirstScan :(hasPrevious ? ProcessPresenceType.ProcessRun : ProcessPresenceType.ProcessStart));
            this.IsSystem = this._DetectIsSystem();
            this.Exists = true;
        }
        private static void Try(Action action)
        {
            try { action(); }
            catch (Exception exc) { }
        }
        public DateTime Now { get; private set; }
        public int Id { get; private set; }
        public Int32? ParentId { get; private set; }
        public bool IsCurrentProcess { get; private set; }
        public string ProcessName { get; private set; }
        public int ThreadsCount { get; private set; }
        public Int32? ProcessorAffinity { get; private set; }
        public Int32? ModulesCount { get; private set; }
        public long WorkingSetKb64 { get; private set; }
        public long PrivateMemorySizeKb64 { get; private set; }
        public string MainWindowTitle { get; private set; }
        public TimeSpan TotalProcessorTime { get; private set; }
        public TimeSpan UserProcessorTime { get; private set; }
        public DateTime? StartTime { get; private set; }
        public string FileName { get; private set; }
        public string MainModuleName { get; private set; }
        public string Arguments { get; private set; }
        public string Domain { get; private set; }
        public string UserName { get; private set; }
        public string MachineName { get; private set; }


        public ProcessPresenceType ProcessPresence { get; private set; }
        public bool IsSystem { get; private set; }
        /// <summary>
        /// Příznak pro scanovací mechanismus, detekuje procesy které existovaly v minulém scanu a nyní již neexistují.
        /// </summary>
        public bool Exists { get; set; }
        /// <summary>
        /// Byla událost vypisovaná do monitoru?
        /// Podle konfigurace se nemusí vypisovat, pokud je zcela shodná s předchozím stavem.
        /// </summary>
        public bool IsTraced { get; private set; }
        #endregion
        #region IsSystem
        private bool _DetectIsSystem()
        {
            string processName = this.ProcessName;
            if (String.Equals(processName, "svchost", StringComparison.InvariantCultureIgnoreCase)) return true;
            if (String.Equals(processName, "taskhost.exe", StringComparison.InvariantCultureIgnoreCase)) return true;
            if (String.Equals(processName, "system", StringComparison.InvariantCultureIgnoreCase)) return true;
            if (String.Equals(processName, "services", StringComparison.InvariantCultureIgnoreCase)) return true;
            if (String.Equals(processName, "wininit", StringComparison.InvariantCultureIgnoreCase)) return true;
            if (String.Equals(processName, "csrss", StringComparison.InvariantCultureIgnoreCase)) return true;
            return false;
        }
        #endregion
    }
    public enum ProcessPresenceType
    {
        None = 0,
        FirstScan,
        ProcessStart,
        ProcessRun,
        ProcessExit
    }
    #endregion
    #region class ProcessPairInfo : data o dvou po sobě jdoucích obrazech téhož procesu
    /// <summary>
    /// ProcessPairInfo : data o dvou po sobě jdoucích obrazech téhož procesu.
    /// Umožňuje zapisovat potřebné informace o vývoji a vynechávat nezměněné zápisy.
    /// </summary>
    public class ProcessPairInfo
    {
        #region Konstrukce, property
        public ProcessPairInfo(DateTime? previousTime, ProcessInfo previous, ProcessInfo current)
        {
            this.PreviousTime = previousTime;
            this.PreviousInfo = previous;
            this.CurrentInfo = current;
        }
        public DateTime? PreviousTime { get; private set; }
        public ProcessInfo PreviousInfo { get; private set; }
        public bool HasPrevious { get { return (this.PreviousInfo != null); } }
        public ProcessInfo CurrentInfo { get; private set; }
        #endregion
        #region TraceLine
        public string TraceLine
        {
            get
            {
                decimal totalCpuUsage = 0m;
                decimal userCpuUsage = 0m;
                TimeSpan realTime = TimeSpan.Zero;
                if (this.HasPrevious)
                    realTime = this.CurrentInfo.Now - this.PreviousInfo.Now;
                else if (this.PreviousTime.HasValue)
                    realTime = this.CurrentInfo.Now - this.PreviousTime.Value;
                if (realTime.Ticks > 0L && this.HasPrevious)
                {
                    TimeSpan time;
                    time = this.CurrentInfo.TotalProcessorTime - this.PreviousInfo.TotalProcessorTime;
                    totalCpuUsage = 100m * (decimal)time.TotalMilliseconds / (decimal)realTime.TotalMilliseconds;
                    time = this.CurrentInfo.UserProcessorTime - this.PreviousInfo.UserProcessorTime;
                    userCpuUsage = 100m * (decimal)time.TotalMilliseconds / (decimal)realTime.TotalMilliseconds;
                }

                string line = Format(this.CurrentInfo.Now) + TAB +
                              Format(this.CurrentInfo.Id) + TAB +
                              Format(this.CurrentInfo.ParentId) + TAB +
                              Format(this.CurrentInfo.ProcessName) + TAB +
                              Format(this.CurrentInfo.WorkingSetKb64) + TAB +
                              Format(this.CurrentInfo.PrivateMemorySizeKb64) + TAB +
                              Format(totalCpuUsage) + TAB +
                              Format(userCpuUsage) + TAB +
                              Format(this.CurrentInfo.ThreadsCount) + TAB +
                              Format(this.CurrentInfo.ModulesCount) + TAB +
                              Format(this.CurrentInfo.ProcessorAffinity) + TAB +
                              Format(this.CurrentInfo.MainWindowTitle) + TAB +
                              Format(this.CurrentInfo.StartTime) + TAB +
                              Format(this.CurrentInfo.FileName) + TAB +
                              Format(this.CurrentInfo.MainModuleName) + TAB +
                              Format(this.CurrentInfo.Arguments) + TAB +
                              Format(this.CurrentInfo.Domain) + TAB +
                              Format(this.CurrentInfo.MachineName) + TAB +
                              Format(this.CurrentInfo.UserName);
                return line;
            }
        }
        private const string _TITLE = 
            "Time;PID;ParentPID;Name;WorkingSet;PrivateMemorySize;TotalProcessorUsage;UserProcessorUsage;" +
            "ThreadsCount;ModulesCount;ProcessorAffinity;" +
            "WindowTitle;StartTime;FileName;Arguments;MainModuleName;Domain;MachineName;UserName";
        public static string TraceTitle { get { return _TITLE.Replace(";", TAB); } }
        #endregion
        #region Formátování
        private static string Format(string text)
        {
            if (String.IsNullOrEmpty(text))
                return "";
            return text
                .Replace('\t', ' ')
                .Replace('\r', ' ')
                .Replace('\n', ' ');
        }
        private static string Format(DateTime date)
        {
            return date.ToString(FMT_DATE);
        }
        private static string Format(DateTime? date)
        {
            if (date.HasValue)
                return date.Value.ToString(FMT_DATE);
            return "";
        }
        private static string Format(TimeSpan time)
        {
            return Format((decimal)time.TotalMilliseconds);
        }
        private static string Format(decimal value)
        {
            return value.ToString().Trim().Replace(".", ",");
        }
        private static string Format(int number)
        {
            return number.ToString();
        }
        private static string Format(Int32? number)
        {
            if (number.HasValue)
                return number.Value.ToString();
            return "";
        }
        private static string Format(long number)
        {
            return number.ToString();
        }
        private const string FMT_DATE = "yyyy-MM-dd HH:mm:ss,fff";
        private const string TAB = "\t";
        public const string TITLE = "Time;PID;Name;WorkingSet;FileName;MainTitle;StartTime;TotalProcessorTime;UserProcessorTime";
        #endregion
        #region Systémový řádek
        internal static void WriteSystemLine(System.IO.StreamWriter writter, DateTime now, string cpu, string ram)
        {
            string empty = "" + TAB;
            string line = Format(now) + TAB +
                "SYS" + TAB +
                empty +
                "summary" + TAB +
                ram +
                empty +
                cpu + TAB +
                empty +
                "";
            writter.WriteLine(line);
        }
        #endregion
    }
	#endregion
}
